package net.android.gainer;

import java.util.concurrent.TimeoutException;
import android.os.Message;
import android.util.Log;

/**
 * Serial class
 *
 * @author tomo＠android-group.jp
 * 
 * @version 0.1
 * @lisence New BSD
 * 
 */
public class Serial {
	private static final String TAG = "Serial";
	
	// デバッグフラグ
	private boolean DEBUG = false;
	// Gainerへのコマンドタイムアウト時間
	private static final long TIMEOUT = 1000;
	// ポート監視ポーリング時間(デフォルト)
	private static final long SLEEP_TIME = 30;
	// 送受信する文字列の最大値
	private static final int MAX_LENGTH = 255;
 
	// Gainerオブジェクト
	private Gainer mGainer = null;
	// 使用するポート名
	private String mPortName;
	// Gainerとの接続ステータス
	private boolean isConnect = false;
	// デジタル連続取り込みフラグ
	private boolean digitalInputting = false;
	// アナログ連続取り込みフラグ
	private boolean analogInputting = false;
	// シリアルポート監視用スレッド
	private Thread portListener = null;
	// ポート監視ポーリング時間(初期値)
	private long mInterval = SLEEP_TIME;

	// JNIライブラリのロード
	static {
		// Gainerライブラリをロード("libgainer.so")
		// "/system/lib" 以下に配置すれば動作する
		System.loadLibrary("gainer");
	}

	/**
	 * シリアルコントロール
	 * 
	 * @param gainer   : Gainerクラスオブジェクト
	 * @param portName : Gainerを接続したポート名
	 * @param debug    : デバッグフラグ (Logcat出力)
	 */
	Serial(Gainer gainer, String portName, boolean debug) {
		this.mGainer = gainer;
		this.mPortName = portName;
		this.DEBUG = debug;
	}
	
	/**
	 * シリアルコントロール
	 * 
	 * @param gainer   : Gainerクラスオブジェクト
	 * @param portName : Gainerを接続したポート名
	 */
	Serial(Gainer gainer, String portName) {
		this(gainer, portName, false);
	}

	/**
	 * <p>Connect Gainer Serial Port. If you can't connect Gainer, you check the
	 * activation of both Gainer device and serial port.</p>
	 * <p>Gainerと接続する</p>
	 * 
	 * @return true:connect / false:disconnect
	 */
	public boolean connectGainer() {
		// 何度もオープンされないように
		if (isConnect) {
			return true;
		}
		// シリアルポートのオープン
		if (!openPort(mPortName, DEBUG)) {
			Gainer.LogE("open port error");
			isConnect = false;
			return false;
		}

		// シリアルポート監視スレッドの生成
		portListener = new Thread(new PortInputListener());
		Gainer.LogI("Success connect Gainer");

// send reboot command to gainer because of changing configuration.
		try {
			sendGainer("Q*", "Q");
		} catch (TimeoutException e) {
			e.printStackTrace();
			Gainer.LogE("configration command timeout");
			closePort();
			isConnect = false;
			return false;
		}

		// Gainerとの接続が確定
		isConnect = true;
		
		return true;
	}

	/**
	 * Gainerとの接続を解除する
	 */
	public void disconnectGainer() {
		isConnect = false;
		portListener = null;
		Gainer.sleep(100);
		closePort();
	}
	
	/**
	 * シリアルポートからのGainer入力の監視を開始する
	 */
	void startPortInputListener() {
		if (portListener != null) {
			Gainer.LogI("Start port input listener");
			portListener.start();
		}
	}

	/**
	 * シリアルポートからのGainerの入力を監視する
	 */
	final class PortInputListener implements Runnable {

		public void run() {
			if (portListener == null) {
				Gainer.LogE("SerialInputListener can't start.");
				return;
			}

			while (isConnect) {

				// ポーリング時間を調整
				Gainer.sleep(mInterval);

				if (analogInputting) {
					mGainer.peekAnalogInput();
				}
				
				if (digitalInputting) {
					mGainer.peekDigitalInput();
				}

				String code;
				try {
					code = readGainer(TIMEOUT);
					if (code != null) {
						Message msg = new Message();
						msg.obj = code;
						mGainer.sendMessage(msg);
					}
				} catch (Exception e) {
					Gainer.LogE("Gainer Read Error");
					return;
				}
			}
			Gainer.LogI("Finish SerialInputListener");			
		}
	}
	
	/**
	 * Send a code to Gainer and receive the return code.
	 * 
	 * @param code
	 *            Gainer protocol code.
	 * @param answer
	 *            <p>
	 *            true:Return the gainer's echo code
	 *            </p>
	 *            <p>
	 *            false:Don't return the gainer's echo code
	 *            </p>
	 * @param receiveResponse
	 *            true:receive gainer's response
	 * @return Gainer's echo code.
	 * @throws TimeoutException
	 *             connection timeout
	 * @throws IOException
	 */
	String sendGainer(String code, boolean answer, boolean receiveResponse) throws TimeoutException {
		byte[] data = new byte[MAX_LENGTH];
		data = code.getBytes();
		Gainer.LogI("sendGainer " + code);

		writePort(data, code.length());
		String returnCode = "";

		if (receiveResponse) {
			returnCode = readGainer(TIMEOUT);
		}

		if (answer) {
			return returnCode;
		}

		return "";
	}

	/**
	 * Send a code to Gainer and check correction of the return code.
	 * 
	 * @param code
	 *            Gainer protocol code.
	 * @param returnCode
	 *            Gainer's echo code.
	 * @throws TimeoutException
	 * @throws IOException
	 */
	void sendGainer(String code, String returnCode) throws TimeoutException {
		String answer = sendGainer(code, true, true);
			// check the return code.
		if (answer.indexOf(returnCode) == -1) {
			Gainer.LogE("Gainer's echo code isn't correct.");
//			throw new IOException();
		}
	}

	/**
	 * Read Gainer return code.
	 * 
	 * @param timeout
	 *            Continue read Gainer return code for <b>timeout</b>.
	 * @return Gainer return code.
	 * @throws TimeoutException
	 *             timeout.
	 */
	private String readGainer(long timeout) throws TimeoutException {
		String returnCode = "";
		byte[] data = new byte[MAX_LENGTH];
		long start = System.currentTimeMillis();
		
		while (true) {
			long now = System.currentTimeMillis();
			long rest = timeout - (now - start);

			// timeout logic
			if (rest <= 0) {
				Gainer.LogE("Timeout");
				throw new TimeoutException("TimeoutException!! "
						+ (now - start));
			}

			Gainer.sleep(100);

			// データ受信
			int size = readPort(data, MAX_LENGTH);
			returnCode = new String(data, 0, size);
			if (returnCode != null && returnCode.indexOf("*") != -1) {
					break;// Get gainer response. So, escape loop.
			}
		}
		return returnCode;
	}
	
	/**
	 * デジタル連続取り込み開始
	 */
	void beginDigitalInput() {
		mInterval = SLEEP_TIME;
		digitalInputting = true;
	}

	/**
	 * デジタル連続取り込み開始(ポーリング時間指定)
	 */
	void beginDigitalInput(long interval) {
		mInterval = interval;
		digitalInputting = true;
	}
	
	/**
	 * デジタル連続取り込み停止
	 */
	void endDigitalInput() {
		digitalInputting = false;
	}

	/**
	 * アナログ連続取り込み開始
	 */
	void beginAnalogInput() {
		mInterval = SLEEP_TIME;
		analogInputting = true;
	}

	/**
	 * アナログ連続取り込み開始(ポーリング時間指定)
	 */
	void beginAnalogInput(long interval) {
		mInterval = interval;
		analogInputting = true;
	}
	
	/**
	 * アナログ連続取り込み停止
	 */
	void endAnalogInput() {
		analogInputting = false;
	}
	
	/**
	 * ネイティブコール関数
	 */
	
	/**
	 * ポートをオープンする
	 * 
	 * @param portName : Gainerを接続したシリアルポート名
	 * @param debug    : デバッグフラグ
	 * 
	 * @return true  : 成功
	 * @return false : 失敗 
	 */
	private native boolean openPort(String portName, boolean debug);
	
	/**
	 * ポートをクローズする
	 */
	private native void closePort();
	
	/**
	 * データの送信を行う
	 * 
	 * @param data : 送信バイト列
	 * @param len  : 送信サイズ
	 * 
	 * @return 成功 > 0
	 * @return 失敗 <= 0
	 */
	private native int writePort(byte data[], int len);

	/**
	 * データの受信を行う
	 * 
	 * @param data : 受信バイト列
	 * @param len  : 受信サイズ
	 * 
	 * @return 成功 > 0  : 受信サイズ
	 * @return 失敗 <= 0
	 */
	private native int readPort(byte data[], int len);
}
